Oracle NetSuite
This guide walks you through the steps required to connect Docflo.ai to your Oracle NetSuite system, enabling seamless document integration and automated invoice processing.
- Oracle NetSuite account with SuiteScript and REST API access
- Visual Studio or development environment for .NET
- Docflo.ai account with API access
- NetSuite user with appropriate roles and permissions
- SSL certificate management capabilities
- NetSuite Integration Record and Token-Based Authentication setup
π Integration Stepsβ
Follow these steps to establish a secure connection between Docflo.ai and your Oracle NetSuite system:
Step 1: Import SSL Certificate Chainβ
- Download our SSL certificate chain (link)
- Import the certificate to the Windows Certificate Store on your integration server
- Add the certificate to Trusted Root Certification Authorities
- Ensure the certificate is properly validated for HTTPS connections
Step 2: Configure NetSuite Token-Based Authenticationβ
- Navigate to Setup > Integration > Manage Integrations in NetSuite
- Create a new Integration Record:
- Name: Docflo Integration
- Description: Integration with Docflo.ai for document processing
- State: Enabled
- Token-Based Authentication: Checked
- Save and note down:
- Consumer Key
- Consumer Secret
- Create Access Token:
- Go to Setup > Users/Roles > Access Tokens
- Click "New Access Token"
- Select the Integration Record created above
- Select appropriate user and role
- Note down Token ID and Token Secret
Step 3: Configure NetSuite User Permissionsβ
Ensure your integration user has the following permissions:
-
Transactions:
- Vendor Bill: Create, Edit, View
- Invoice: Create, Edit, View
- Purchase Order: View
- Sales Order: View
-
Lists:
- Vendors: Create, Edit, View
- Customers: Create, Edit, View
- Items: View
- Accounts: View
-
Setup:
- REST Web Services: Full
- SuiteScript: Full
Step 4: Generate API Credentialsβ
- Go to the "Integrations" section in your Docflo.ai platform
- Create an API key for NetSuite integration
- Copy the API key and store it securely
- Copy the tenant ID as well - you'll need both for the integration
Step 5: Create NetSuite Integration Serviceβ
Create a Windows Service or scheduled application using NetSuite's REST API with OAuth 1.0. Here's a sample C# implementation:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Linq;
using System.Security.Cryptography;
using System.Web;
namespace DocfloNetSuiteIntegration
{
public class DocfloNetSuiteIntegrationService
{
private readonly string _docfloApiUrl;
private readonly string _tenantId;
private readonly string _apiKey;
private readonly string _netSuiteAccountId;
private readonly string _consumerKey;
private readonly string _consumerSecret;
private readonly string _tokenId;
private readonly string _tokenSecret;
private readonly string _netSuiteBaseUrl;
private readonly HttpClient _httpClient;
private readonly HttpClient _netSuiteClient;
public DocfloNetSuiteIntegrationService(string docfloApiUrl, string tenantId, string apiKey,
string netSuiteAccountId, string consumerKey, string consumerSecret,
string tokenId, string tokenSecret)
{
_docfloApiUrl = docfloApiUrl;
_tenantId = tenantId;
_apiKey = apiKey;
_netSuiteAccountId = netSuiteAccountId;
_consumerKey = consumerKey;
_consumerSecret = consumerSecret;
_tokenId = tokenId;
_tokenSecret = tokenSecret;
_netSuiteBaseUrl = $"https://{_netSuiteAccountId}.suitetalk.api.netsuite.com/rest/platform/v1";
// Initialize Docflo HTTP client
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("x-tenant-id", _tenantId);
_httpClient.DefaultRequestHeaders.Add("apiKey", _apiKey);
// Initialize NetSuite HTTP client
_netSuiteClient = new HttpClient();
_netSuiteClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private string GenerateOAuthHeader(string httpMethod, string url)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var nonce = Guid.NewGuid().ToString("N");
var parameters = new SortedDictionary<string, string>
{
{"oauth_consumer_key", _consumerKey},
{"oauth_nonce", nonce},
{"oauth_signature_method", "HMAC-SHA256"},
{"oauth_timestamp", timestamp},
{"oauth_token", _tokenId},
{"oauth_version", "1.0"}
};
var parameterString = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
var signatureBaseString = $"{httpMethod.ToUpper()}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(parameterString)}";
var signingKey = $"{Uri.EscapeDataString(_consumerSecret)}&{Uri.EscapeDataString(_tokenSecret)}";
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingKey)))
{
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureBaseString)));
parameters.Add("oauth_signature", signature);
}
var authHeader = "OAuth " + string.Join(", ", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}=\"{Uri.EscapeDataString(p.Value)}\""));
return authHeader;
}
public async Task<List<DocfloDocument>> GetDocfloDocuments(string documentType, bool includeResults = true)
{
try
{
string url = $"{_docfloApiUrl}/docs/v1/document?type={documentType}&includeResults={includeResults}";
HttpResponseMessage response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string jsonContent = await response.Content.ReadAsStringAsync();
var docfloResponse = JsonConvert.DeserializeObject<DocfloResponse>(jsonContent);
return docfloResponse.Data;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching Docflo documents: {ex.Message}");
return new List<DocfloDocument>();
}
}
public async Task<bool> CreateVendorBill(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);
// Ensure vendor exists or create one
var vendorId = await GetOrCreateVendor(extractedData.VendorName, extractedData.VendorCode);
// Create Vendor Bill payload for NetSuite
var billPayload = new
{
entity = new { id = vendorId },
tranDate = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
tranId = extractedData.InvoiceNumber,
memo = $"Vendor bill processed from Docflo document {document.Id}",
currency = new { internalId = GetCurrencyId(extractedData.CurrencyCode) },
exchangeRate = 1.0,
// Custom field for Docflo tracking
custbody_docflo_document_id = document.Id,
custbody_docflo_source_id = document.SourceId,
item = new
{
items = extractedData.LineItems.Select((line, index) => new
{
item = new { internalId = GetOrCreateItem(line.ItemCode, line.Description).Result },
quantity = line.Quantity,
rate = line.UnitPrice,
amount = line.LineTotal,
description = line.Description,
account = new { internalId = GetDefaultExpenseAccount() }
}).ToArray()
}
};
string jsonPayload = JsonConvert.SerializeObject(billPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string netSuiteUrl = $"{_netSuiteBaseUrl}/record/vendorbill";
// Add OAuth header
var authHeader = GenerateOAuthHeader("POST", netSuiteUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authHeader);
HttpResponseMessage response = await _netSuiteClient.PostAsync(netSuiteUrl, content);
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Vendor Bill created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating Vendor Bill: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating Vendor Bill: {ex.Message}");
return false;
}
}
public async Task<bool> CreateCustomerInvoice(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);
// Ensure customer exists or create one
var customerId = await GetOrCreateCustomer(extractedData.CustomerName, extractedData.CustomerCode);
// Create Customer Invoice payload for NetSuite
var invoicePayload = new
{
entity = new { id = customerId },
tranDate = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
tranId = extractedData.InvoiceNumber,
memo = $"Customer invoice processed from Docflo document {document.Id}",
currency = new { internalId = GetCurrencyId(extractedData.CurrencyCode) },
exchangeRate = 1.0,
// Custom field for Docflo tracking
custbody_docflo_document_id = document.Id,
custbody_docflo_source_id = document.SourceId,
item = new
{
items = extractedData.LineItems.Select((line, index) => new
{
item = new { internalId = GetOrCreateItem(line.ItemCode, line.Description).Result },
quantity = line.Quantity,
rate = line.UnitPrice,
amount = line.LineTotal,
description = line.Description,
taxCode = new { internalId = GetDefaultTaxCode() }
}).ToArray()
}
};
string jsonPayload = JsonConvert.SerializeObject(invoicePayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string netSuiteUrl = $"{_netSuiteBaseUrl}/record/invoice";
// Add OAuth header
var authHeader = GenerateOAuthHeader("POST", netSuiteUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authHeader);
HttpResponseMessage response = await _netSuiteClient.PostAsync(netSuiteUrl, content);
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Customer Invoice created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating Customer Invoice: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating Customer Invoice: {ex.Message}");
return false;
}
}
public async Task<string> GetOrCreateVendor(string vendorName, string vendorCode = null)
{
try
{
// First, try to find existing vendor by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(vendorCode)
? $"SELECT id FROM vendor WHERE entityid = '{vendorCode}'"
: $"SELECT id FROM vendor WHERE companyname = '{vendorName}'"
};
string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");
var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);
HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);
if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}
// Vendor not found, create new one
var vendorPayload = new
{
entityId = vendorCode ?? GenerateVendorCode(vendorName),
companyName = vendorName ?? "Unknown Vendor",
isPerson = false,
subsidiary = new { internalId = "1" } // Default subsidiary
};
string jsonPayload = JsonConvert.SerializeObject(vendorPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string createUrl = $"{_netSuiteBaseUrl}/record/vendor";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);
HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);
if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Vendor created: {vendorPayload.entityId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating vendor: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default vendor ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateVendor: {ex.Message}");
return "1"; // Return default vendor ID
}
}
public async Task<string> GetOrCreateCustomer(string customerName, string customerCode = null)
{
try
{
// First, try to find existing customer by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(customerCode)
? $"SELECT id FROM customer WHERE entityid = '{customerCode}'"
: $"SELECT id FROM customer WHERE companyname = '{customerName}'"
};
string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");
var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);
HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);
if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}
// Customer not found, create new one
var customerPayload = new
{
entityId = customerCode ?? GenerateCustomerCode(customerName),
companyName = customerName ?? "Unknown Customer",
isPerson = false,
subsidiary = new { internalId = "1" } // Default subsidiary
};
string jsonPayload = JsonConvert.SerializeObject(customerPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string createUrl = $"{_netSuiteBaseUrl}/record/customer";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);
HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);
if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Customer created: {customerPayload.entityId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating customer: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default customer ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateCustomer: {ex.Message}");
return "1"; // Return default customer ID
}
}
public async Task<string> GetOrCreateItem(string itemCode, string itemName)
{
try
{
// First, try to find existing item by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(itemCode)
? $"SELECT id FROM item WHERE itemid = '{itemCode}'"
: $"SELECT id FROM item WHERE displayname = '{itemName}'"
};
string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");
var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);
HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);
if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}
// Item not found, create new service item
var itemPayload = new
{
itemId = itemCode ?? GenerateItemCode(itemName),
displayName = itemName ?? "Service Item",
subsidiary = new { internalId = "1" }, // Default subsidiary
incomeAccount = new { internalId = GetDefaultIncomeAccount() },
expenseAccount = new { internalId = GetDefaultExpenseAccount() }
};
string jsonPayload = JsonConvert.SerializeObject(itemPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
string createUrl = $"{_netSuiteBaseUrl}/record/serviceitem";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);
HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);
if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Service item created: {itemPayload.itemId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating item: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default item ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateItem: {ex.Message}");
return "1"; // Return default item ID
}
}
private string GenerateVendorCode(string vendorName)
{
if (string.IsNullOrEmpty(vendorName))
return "VEND" + DateTime.Now.Ticks.ToString().Substring(0, 6);
string vendorCode = vendorName.Replace(" ", "").ToUpper();
if (vendorCode.Length > 10)
vendorCode = vendorCode.Substring(0, 10);
return vendorCode;
}
private string GenerateCustomerCode(string customerName)
{
if (string.IsNullOrEmpty(customerName))
return "CUST" + DateTime.Now.Ticks.ToString().Substring(0, 6);
string custCode = customerName.Replace(" ", "").ToUpper();
if (custCode.Length > 10)
custCode = custCode.Substring(0, 10);
return custCode;
}
private string GenerateItemCode(string itemName)
{
if (string.IsNullOrEmpty(itemName))
return "ITEM" + DateTime.Now.Ticks.ToString().Substring(0, 6);
string itemCode = itemName.Replace(" ", "").ToUpper();
if (itemCode.Length > 15)
itemCode = itemCode.Substring(0, 15);
return itemCode;
}
private string GetCurrencyId(string currencyCode)
{
// Map common currency codes to NetSuite internal IDs
// This should be customized based on your NetSuite setup
switch (currencyCode?.ToUpper())
{
case "USD": return "1";
case "EUR": return "2";
case "GBP": return "3";
case "CAD": return "4";
default: return "1"; // Default to USD
}
}
private string GetDefaultExpenseAccount()
{
// Return default expense account ID - customize based on your chart of accounts
return "120"; // Example: Office Expenses
}
private string GetDefaultIncomeAccount()
{
// Return default income account ID - customize based on your chart of accounts
return "400"; // Example: Sales Revenue
}
private string GetDefaultTaxCode()
{
// Return default tax code ID - customize based on your tax setup
return "1"; // Example: Standard Tax
}
private NetSuiteInvoiceData ExtractInvoiceData(DocfloDocument document)
{
var data = new NetSuiteInvoiceData();
data.LineItems = new List<NetSuiteInvoiceLineItem>();
// Extract data from Docflo results
if (document.DocfloResults?.ModelFields?.Items?.Value != null)
{
foreach (var item in document.DocfloResults.ModelFields.Items.Value)
{
var description = item.ValueObject?.Description;
if (description != null)
{
switch (description.Type?.ToLower())
{
case "vendor_code":
case "supplier_code":
data.VendorCode = description.ValueString;
break;
case "vendor_name":
case "supplier_name":
data.VendorName = description.ValueString;
break;
case "customer_code":
data.CustomerCode = description.ValueString;
break;
case "customer_name":
case "company_name":
data.CustomerName = description.ValueString;
break;
case "invoice_number":
case "document_number":
data.InvoiceNumber = description.ValueString;
break;
case "invoice_date":
case "document_date":
if (DateTime.TryParse(description.ValueString, out DateTime invoiceDate))
{
data.InvoiceDate = invoiceDate;
}
break;
case "currency_code":
case "currency":
data.CurrencyCode = description.ValueString;
break;
case "total":
case "total_amount":
case "gross_amount":
if (double.TryParse(description.ValueString, out double total))
{
data.Total = total;
}
break;
case "item_description":
case "product_description":
case "line_description":
// Create new line item
var lineItem = new NetSuiteInvoiceLineItem
{
Description = description.ValueString,
Quantity = 1, // Default quantity
UnitPrice = 0, // Default price
LineTotal = 0 // Default total
};
data.LineItems.Add(lineItem);
break;
case "item_code":
case "part_number":
// Update last line item code
if (data.LineItems.Count > 0)
{
data.LineItems[data.LineItems.Count - 1].ItemCode = description.ValueString;
}
break;
case "quantity":
// Update last line item quantity
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double quantity))
{
data.LineItems[data.LineItems.Count - 1].Quantity = quantity;
}
break;
case "unit_price":
case "price":
// Update last line item price
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double unitPrice))
{
data.LineItems[data.LineItems.Count - 1].UnitPrice = unitPrice;
}
break;
case "line_total":
case "amount":
// Update last line item total
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double lineTotal))
{
data.LineItems[data.LineItems.Count - 1].LineTotal = lineTotal;
}
break;
}
}
}
}
// Generate codes if not provided
if (string.IsNullOrEmpty(data.CustomerCode) && !string.IsNullOrEmpty(data.CustomerName))
{
data.CustomerCode = GenerateCustomerCode(data.CustomerName);
}
if (string.IsNullOrEmpty(data.VendorCode) && !string.IsNullOrEmpty(data.VendorName))
{
data.VendorCode = GenerateVendorCode(data.VendorName);
}
return data;
}
public void Dispose()
{
_httpClient?.Dispose();
_netSuiteClient?.Dispose();
}
}
// Data models
public class DocfloResponse
{
[JsonProperty("data")]
public List<DocfloDocument> Data { get; set; }
}
public class DocfloDocument
{
[JsonProperty("_id")]
public string Id { get; set; }
[JsonProperty("sourceId")]
public string SourceId { get; set; }
[JsonProperty("sourceDesc")]
public string SourceDesc { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("numOfPages")]
public string NumOfPages { get; set; }
[JsonProperty("createdAt")]
public string CreatedAt { get; set; }
[JsonProperty("docflo_results")]
public DocfloResults DocfloResults { get; set; }
}
public class DocfloResults
{
[JsonProperty("modelFields")]
public ModelFields ModelFields { get; set; }
}
public class ModelFields
{
[JsonProperty("Items")]
public ItemsField Items { get; set; }
}
public class ItemsField
{
[JsonProperty("value")]
public List<Item> Value { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
public class Item
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("valueObject")]
public ValueObject ValueObject { get; set; }
}
public class ValueObject
{
[JsonProperty("Description")]
public Description Description { get; set; }
}
public class Description
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("valueString")]
public string ValueString { get; set; }
[JsonProperty("content")]
public string Content { get; set; }
}
public class NetSuiteSuiteQLResponse
{
[JsonProperty("items")]
public List<NetSuiteSuiteQLItem> Items { get; set; }
}
public class NetSuiteSuiteQLItem
{
[JsonProperty("values")]
public List<string> Values { get; set; }
}
public class NetSuiteCreateResponse
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("success")]
public bool Success { get; set; }
}
public class NetSuiteInvoiceData
{
public string VendorCode { get; set; }
public string VendorName { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string InvoiceNumber { get; set; }
public DateTime? InvoiceDate { get; set; }
public string CurrencyCode { get; set; }
public double Total { get; set; }
public List<NetSuiteInvoiceLineItem> LineItems { get; set; }
}
public class NetSuiteInvoiceLineItem
{
public string ItemCode { get; set; }
public string Description { get; set; }
public double Quantity { get; set; }
public double UnitPrice { get; set; }
public double LineTotal { get; set; }
}
// Main program example
class Program
{
static async Task Main(string[] args)
{
var service = new DocfloNetSuiteIntegrationService(
"https://api.docflo.ai", // Replace with actual Docflo API URL
"your-tenant-id", // Replace with your tenant ID
"your-api-key", // Replace with your API key
"your-netsuite-account-id", // Replace with your NetSuite account ID
"your-consumer-key", // Replace with Consumer Key from Integration Record
"your-consumer-secret", // Replace with Consumer Secret from Integration Record
"your-token-id", // Replace with Token ID from Access Token
"your-token-secret" // Replace with Token Secret from Access Token
);
try
{
// Get documents from Docflo
var documents = await service.GetDocfloDocuments("invoice", true);
foreach (var document in documents)
{
// Process each document based on status
if (document.Status == "APPROVED")
{
// Create Vendor Bill for vendor invoices
bool vendorResult = await service.CreateVendorBill(document);
// Or create Customer Invoice for customer invoices
// bool customerResult = await service.CreateCustomerInvoice(document);
if (vendorResult)
{
Console.WriteLine($"Successfully processed document {document.Id}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in main process: {ex.Message}");
}
finally
{
service.Dispose();
}
}
}
}
π§ Configuration Requirementsβ
NuGet Packages Requiredβ
Add these NuGet packages to your project:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
NetSuite Custom Fields Setupβ
Create custom fields in NetSuite for tracking Docflo documents:
- Transaction Body Custom Fields:
-
Field ID:
custbody_docflo_document_id -
Type: Free-Form Text
-
Label: Docflo Document ID
-
Length: 50
-
Applies To: Vendor Bill, Invoice
-
Field ID:
custbody_docflo_source_id -
Type: Free-Form Text
-
Label: Docflo Source ID
-
Length: 50
-
Applies To: Vendor Bill, Invoice
-
NetSuite User Role Configurationβ
Ensure your integration user role has the following permissions:
-
Transactions:
- Vendor Bill: Full
- Invoice: Full
- Purchase Order: View
- Sales Order: View
-
Lists:
- Vendors: Full
- Customers: Full
- Items: View
- Accounts: View
-
Setup:
- REST Web Services: Full
- SuiteScript: Full
- Integration Management: Full
Application Configurationβ
Create an app.config file with your settings:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="DocfloApiUrl" value="https://api.docflo.ai" />
<add key="TenantId" value="your-tenant-id" />
<add key="ApiKey" value="your-api-key" />
<add key="NetSuiteAccountId" value="your-netsuite-account-id" />
<add key="NetSuiteConsumerKey" value="your-consumer-key" />
<add key="NetSuiteConsumerSecret" value="your-consumer-secret" />
<add key="NetSuiteTokenId" value="your-token-id" />
<add key="NetSuiteTokenSecret" value="your-token-secret" />
</appSettings>
</configuration>
π Deployment Optionsβ
Option 1: Windows Serviceβ
Deploy as a Windows Service for continuous processing:
- Install the service using
sc createor InstallUtil - Configure service account with appropriate permissions
- Set up logging for monitoring and troubleshooting
- Schedule regular document polling
Option 2: Azure Functionβ
Deploy as an Azure Function for cloud-based processing:
- Create Azure Function App
- Configure timer trigger for scheduled execution
- Set application settings for configuration values
- Monitor execution through Azure portal
Option 3: Scheduled Taskβ
Use Windows Task Scheduler for periodic execution:
- Create a scheduled task to run the application
- Set appropriate triggers (hourly, daily, etc.)
- Configure security context for NetSuite access
- Monitor execution logs
π Monitoring and Loggingβ
Implement comprehensive logging using NLog or Serilog:
// Add to your service class
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public void LogIntegrationActivity(string message, Exception ex = null)
{
if (ex == null)
{
logger.Info(message);
}
else
{
logger.Error(ex, message);
}
}
π NetSuite Specific Considerationsβ
OAuth 1.0 Authenticationβ
NetSuite uses OAuth 1.0 for REST API authentication:
- Consumer Key/Secret: From Integration Record
- Token ID/Secret: From Access Token
- Signature Method: HMAC-SHA256
- Account ID: Your NetSuite account identifier
SuiteQL for Searchingβ
NetSuite provides SuiteQL for advanced searching:
- Base URL:
https://account-id.suitetalk.api.netsuite.com/rest/platform/v1/query/v1/suiteql - SQL-like syntax for querying records
- Supports complex WHERE clauses and JOINs
Record Typesβ
NetSuite uses specific record types:
- vendorbill - Vendor Bills (A/P)
- invoice - Customer Invoices (A/R)
- vendor - Vendor master records
- customer - Customer master records
- serviceitem - Service items
- inventoryitem - Inventory items
Internal IDsβ
NetSuite uses internal IDs for record references:
- All record references use
{ internalId: "value" }format - Currency, accounts, subsidiaries all have internal IDs
- Custom fields use field IDs like
custbody_field_name
π Integration Complete! Your Oracle NetSuite system is now connected to Docflo.ai and can automatically process invoice documents, creating both vendor bills and customer invoices based on extracted document data.
π Troubleshootingβ
Common Issuesβ
-
OAuth Authentication Errors:
- Verify Consumer Key and Consumer Secret from Integration Record
- Check Token ID and Token Secret from Access Token
- Ensure proper OAuth signature generation
- Verify account ID in base URL
-
SSL Certificate Issues:
- Verify certificate installation in Windows Certificate Store
- Check certificate chain completeness
- Ensure proper certificate binding
-
API Rate Limiting:
- NetSuite has governance limits for API calls
- Implement retry logic with exponential backoff
- Monitor usage through NetSuite's governance dashboard
-
Data Mapping Issues:
- Validate extracted data from Docflo documents
- Check required fields for NetSuite record creation
- Ensure proper data type conversions
- Verify internal ID mappings for currencies, accounts, etc.
-
SuiteQL Query Issues:
- Ensure proper SQL syntax in SuiteQL queries
- Check field names and table names
- Handle special characters in search values
- Verify user permissions for queried records
-
Custom Field Issues:
- Verify custom fields exist in NetSuite
- Check field IDs match exactly (case-sensitive)
- Ensure fields are enabled for the record types
- Verify user has access to custom fields
π Supportβ
For technical assistance with the Oracle NetSuite integration:
- Contact Docflo.ai support team
- Consult NetSuite SuiteCloud documentation
- Review NetSuite REST API guides
- Contact your NetSuite administrator or implementation partner
- Visit NetSuite Help Center for SuiteScript and REST API resources